package com.beanpostprocessor;

import com.MyBeanFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

@Slf4j
public class AutowiredPostProcessor implements SmartInstantiationAwareBeanPostProcessor {

    private final Set<Class<? extends Annotation>> autowiredAnnotationTypes = new LinkedHashSet<>(4);

    private MyBeanFactory beanFactory;

    public AutowiredPostProcessor(MyBeanFactory beanFactory) {
        this.autowiredAnnotationTypes.add(Autowired.class);
        this.autowiredAnnotationTypes.add(Value.class);
        this.beanFactory = beanFactory;

    }

    @Override
    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {

        final List<PropertyDescriptor> propertyDescriptors = getPropertyDescriptors(bean.getClass());

        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            final Object param = beanFactory.getBean(propertyDescriptor.getPropertyType().getName());
            try {
                propertyDescriptor.getWriteMethod().invoke(bean,param);
            } catch (IllegalAccessException | InvocationTargetException e) {
                throw new FatalBeanException(e.getMessage());
            }
        }


        return pvs;
    }

    private List<PropertyDescriptor> getPropertyDescriptors(Class<?> targetClass) {

        List<PropertyDescriptor> propertyDescriptors = new ArrayList<>();

        ReflectionUtils.doWithLocalMethods(targetClass, method -> {
            Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
            if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
                return;
            }
            MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
            if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, targetClass))) {
                if (Modifier.isStatic(method.getModifiers())) {
                    if (log.isInfoEnabled()) {
                        log.info("Autowired annotation is not supported on static methods: " + method);
                    }
                    return;
                }
                if (method.getParameterCount() == 0) {
                    if (log.isInfoEnabled()) {
                        log.info("Autowired annotation should only be used on methods with parameters: " +
                                method);
                    }
                }
                //boolean required = determineRequiredStatus(ann);
                PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, targetClass);
                propertyDescriptors.add(pd)        ;
            }
        });

        return propertyDescriptors;
    }

    private MergedAnnotation<?> findAutowiredAnnotation(AccessibleObject ao) {
        MergedAnnotations annotations = MergedAnnotations.from(ao);
        for (Class<? extends Annotation> type : this.autowiredAnnotationTypes) {
            MergedAnnotation<?> annotation = annotations.get(type);
            if (annotation.isPresent()) {
                return annotation;
            }
        }
        return null;
    }
}
